package clue.players.ai;

import java.util.HashMap;
import java.util.List;
import java.util.Set;

import org.javatuples.Triplet;

import clue.EnumerationEngine2;
import clue.cards.Card;
import clue.cards.RoomCard;
import clue.cards.SuspectCard;
import clue.cards.WeaponCard;
import clue.engine.GameState;
import clue.enums.Room;
import clue.enums.Suspect;
import clue.enums.Weapon;
import clue.players.Player;

public class StrongAIPlayer extends Player {

	private EnumerationEngine2 engine;
	private HashMap<Integer, Integer> IDToOpponentIdx;
	private int id;
	@Override
	public void gameStarted(GameState gs) {
		Card[] cards = new Card[gs.getOwnHand(this).size()];
		gs.getOwnHand(this).toArray(cards);
		engine = new EnumerationEngine2(gs.playerIDs.size(), cards);
		IDToOpponentIdx = new HashMap<>();
		int myID = gs.getOwnID(this);
		id = myID;
		List<Integer> ids = gs.playerIDs;
		int i;
		for (i = 0; i < ids.size(); i++) {
			if (ids.get(i) == myID)
				break;
		}
		int index = 0;
		for (int j = (i + 1) % ids.size(); j != i; j = (j + 1) % ids.size(), index++) {
			IDToOpponentIdx.put(ids.get(j), index);
		}
	}
	private boolean firstTime = true;
	private double lastEntropy;
	private double expectedEntropy;
	private int suspect;
	private int weapon;
	
	public int getID() {
		return id;
	}
	
	@Override
	public Room move(GameState gs) {
		//System.out.println("Player " + gs.getOwnID(this) + " entropy: " + engine.entropy);
				
		int room = -1, suspect = -1, weapon = -1;
		
		if (firstTime) {
			room = 0;
			suspect = 0;
			weapon = 0;
			firstTime = false;
			return Room.values()[room];
		} else {
		
		long ownHand = engine.ourHand;
		double minProd = Double.MAX_VALUE;
		engine.calculateReactions();
		for (int r = 0; r < 9; r++) {
			if (Room.values()[r].equals(gs.IDToLocation.get(gs.getOwnID(this))))
				continue;
			if (((1l << (r + 12)) & ownHand) != 0)
				continue;
			for (int s = 0; s < 6; s++) {
				if (((1l << (s + 6)) & ownHand) != 0)
					continue;
				for (int w = 0; w < 6; w++) {
					if (((1l << w) & ownHand) != 0)
						continue;
					double expected_entropy = 0;
					double variance = 0;
					double count = 0;
					for (clue.EnumerationEngine2.Particle p : engine.particles) {
						int o;
						boolean refute = false;
						for (o = 0; o < gs.playerIDs.size() - 1; o++) {
							if (p.exists(1l << (r + 12) , o)) {
								expected_entropy += engine.reactionEntropies[r][s][w][r+12][o];
								count++;
								refute = true;
							}
							if (p.exists(1l << (s + 6), o)) {
								expected_entropy += engine.reactionEntropies[r][s][w][s+6][o];
								count++;
								refute = true;
							}
							if (p.exists(1l << w, o)) {
								expected_entropy += engine.reactionEntropies[r][s][w][w][o];
								count++;
								refute = true;
							}
							if (refute)
								break;
						}
							//System.out.println(engine.reactionEntropies[r][s][w][l][o]);
						if (!refute) // this means no one can refute
							count++;
						 // else expected entropy is 0
					}
					expected_entropy /= count;
			
					for (clue.EnumerationEngine2.Particle p : engine.particles) {
						int o;
						boolean refute = false;
						for (o = 0; o < gs.playerIDs.size() - 1; o++) {
							if (p.exists(1l << (r + 12) , o)) {
								double ent = expected_entropy - engine.reactionEntropies[r][s][w][r+12][o];
								variance += ent * ent;
								refute = true;
							}
							if (p.exists(1l << (s + 6), o)) {
								double ent = expected_entropy - engine.reactionEntropies[r][s][w][s + 6][o];
								variance += ent * ent;
								refute = true;
							}
							if (p.exists(1l << w, o)) {
								double ent = expected_entropy - engine.reactionEntropies[r][s][w][w][o];
								variance += ent * ent;
								refute = true;
							}
							if (refute)
								break;
						}
					}
					//System.out.println(expected_entropy);
					variance /= count;
					//System.out.println(expected_entropy + "," + variance);
					
					if (variance < 0.3 && expected_entropy < minProd) {
						minProd = expected_entropy;
						room = r;
						suspect = s;
						weapon = w;
						
					}
				}
			}
		}
	
		//System.out.printf("%d, %d, %d, %f\n", room, suspect, weapon, minExpectedEntropy / engine.particles.size() );
		
		}
		if (room == -1) {
			for (int r = 0; r < 9; r++) {
				if (Room.values()[r].equals(gs.IDToLocation.get(gs.getOwnID(this))))
					continue;
				room = r;
				suspect = 0;
				weapon = 0;
				break;
			}
		}
		
		this.suspect = suspect;
		this.weapon = weapon;
		
		
		return Room.values()[room];
	}

	@Override
	public Triplet<Room, Suspect, Weapon> suggest(GameState gs) {
		return new Triplet<Room, Suspect, Weapon>(gs.IDToLocation.get(gs.getOwnID(this)), Suspect.values()[suspect],Weapon.values()[weapon]);
	}

	@Override
	public Card refute(GameState gs, Integer suggestingPlayerID,
			Triplet<Room, Suspect, Weapon> suggestion) {
		for (Card c : gs.getOwnHand(this)) {
			if (c instanceof RoomCard) {
				RoomCard rc = (RoomCard) c;
				if (rc.getRoom().equals(suggestion.getValue0()))
					return rc;
			} else if (c instanceof SuspectCard) {
				SuspectCard sc = (SuspectCard) c;
				if (sc.getSuspect().equals(suggestion.getValue1()))
					return sc;
			} else if (c instanceof WeaponCard) {
				WeaponCard wc = (WeaponCard) c;
				if (wc.getWeapon().equals(suggestion.getValue2()))
					return wc;
			}
		}
		return null;
	}

	@Override
	public void informMove(GameState gs, Integer movingPlayerID, Room room) {
	}

	@Override
	public void informSuggestion(GameState gs, Integer suggestingPlayerID,
			Triplet<Room, Suspect, Weapon> suggestion) {
		// we'll assume no trickery for now
		// TODO: do not assume no trickery
//		engine.mustNotExist(new Card[] { new RoomCard(suggestion.getValue0()),
//										 new SuspectCard(suggestion.getValue1()),
//										 new WeaponCard(suggestion.getValue2())
//									   },
//							this.IDToOpponentIdx.get(suggestingPlayerID));
	}

	@Override
	public void informFailureToRefute(GameState gs, Integer failedPlayerID,
			Triplet<Room, Suspect, Weapon> suggestion) {
		engine.mustNotExist(new Card[] { new RoomCard(suggestion.getValue0()),
				 						 new SuspectCard(suggestion.getValue1()),
				 						 new WeaponCard(suggestion.getValue2())
			   						   },
			   				this.IDToOpponentIdx.get(failedPlayerID));
	}

	@Override
	public void informRefutation(GameState gs, Integer refutingPlayerID,
			Card refutingCard) {
		engine.mustExist(refutingCard, this.IDToOpponentIdx.get(refutingPlayerID));
		//if (lastEntropy > 0)
		//	System.out.printf("%f %f\n", this.expectedEntropy, lastEntropy - engine.entropy);
	}

	@Override
	public void informRefutationHidden(GameState gs, Integer refutingPlayerID,
			Integer suggestingPlayerID,
			Triplet<Room, Suspect, Weapon> suggestion) {
		engine.opponentReaction(new Card[] {
									new RoomCard(suggestion.getValue0()),
				 					new SuspectCard(suggestion.getValue1()),
				 					new WeaponCard(suggestion.getValue2())
			   				    },
			   				    this.IDToOpponentIdx.get(refutingPlayerID),
			   				    true);
	}

	@Override
	public void gameEnded(GameState gs, Integer winnerID,
			Triplet<Room, Suspect, Weapon> winningAccusation) {
		/*if (winnerID == gs.getOwnID(this))
			System.out.println("Player " + gs.getOwnID(this) + ": We won!");
		else 
			System.out.println("Player " + gs.getOwnID(this) + ": We lost...");*/
	}

	@Override
	public void informFailedAccusation(GameState gs, Integer failedPlayerID,
			Triplet<Room, Suspect, Weapon> failedAccusation) {
		// TODO: add in accusation stuff
	}

	@Override
	public Triplet<Room, Suspect, Weapon> accuse(GameState gs) {
		for (int r = 0; r < 9; r++) {
			for (int s = 0; s < 6; s++)
				for (int w = 0; w < 6; w++) {
					if (engine.envProb[w][s][r] > 0.95)
						return new Triplet<Room, Suspect, Weapon>(Room.values()[r], 
								Suspect.values()[s], 
								Weapon.values()[w]);
				}
		}
		return null;
	}

}
